package com.bss.service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.Query;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.andy.basics.Dao;
import com.bss.dao.AdmissionDao;
import com.bss.dao.BedBookingDao;
import com.bss.dao.RoomBookingDao;
import com.bss.model.Bed;
import com.bss.model.BedBooking;
import com.bss.model.Building;
import com.bss.model.Room;
import com.bss.model.RoomBooking;
import com.bss.model.RoomOccupancy;

@Service
public class RoomOccupancyService extends com.andy.basics.Service<RoomOccupancy> {

	private static final Logger LOGGER = Logger.getLogger(RoomOccupancyService.class.getName());

	private RoomBooking roomBooking;

	private BedBooking bedBooking;

	@Autowired
	private RoomBookingDao roomBookingDao;

	@Autowired
	private BedBookingDao bedBookingDao;

	@Autowired
	private AdmissionDao admissionDao;

	@Autowired
	private BuildingService buildingService;

	private List<Building> buildings;

	private List<Integer> occupiedRoomKeys;

	private List<Integer> occupiedBedKeys;

	public List<Integer> findOccupiedBedKeys() {
		Query q2 = this.getGenericDao().getEntityManager()
				.createQuery("Select b from Bed b join b.bedBookings as bb where bb.checkOutDate is null");

		ArrayList<Integer> occupiedBedKeys = new ArrayList<Integer>();
		for (Bed bed : (List<Bed>) q2.getResultList()) {
			occupiedBedKeys.add(bed.getId());
		}
		return occupiedBedKeys;
	}

	public List<Integer> findOccupiedRoomKeys() {
		Query q1 = this.getGenericDao().getEntityManager()
				.createQuery("Select r from Room r join r.roomBookings as rb where rb.checkOutDate is null");

		ArrayList<Integer> occupiedRoomKeys = new ArrayList<Integer>();
		for (Room room : (List<Room>) q1.getResultList()) {
			occupiedRoomKeys.add(room.getId());
		}
		return occupiedRoomKeys;
	}

	public List<Building> getBuildings() {
		this.buildings = this.buildingService.getBuildings();
		this.occupiedRoomKeys = this.findOccupiedRoomKeys();
		this.occupiedBedKeys = this.findOccupiedBedKeys();
		this.populateOccupancy(this.buildings, this.occupiedRoomKeys, this.occupiedBedKeys);
		return this.buildings;
	}

	@Override
	public Dao<RoomOccupancy> getDao() {
		throw new UnsupportedOperationException();
	}

	public void setRoomBooking(final RoomBooking roomBooking) {
		this.roomBooking = roomBooking;
	}

	public RoomBooking getRoomBooking() {
		if (this.roomBooking == null) {
			this.roomBooking = new RoomBooking();
		}
		return this.roomBooking;
	}

	public void setBedBooking(final BedBooking bedBooking) {
		this.bedBooking = bedBooking;
	}

	public BedBooking getBedBooking() {
		if (this.bedBooking == null) {
			this.bedBooking = new BedBooking();
		}
		return this.bedBooking;
	}

	public void populateOccupancy(final List<Building> buildings) {
		this.populateOccupancy(buildings, this.occupiedRoomKeys, this.occupiedBedKeys);
	}

	public void populateOccupancy(final List<Building> buildings, List<Integer> occupiedRoomKeys, List<Integer> occupiedBedKeys) {
		for (Building building : buildings) {
			for (Room room : building.getRooms()) {

				room.setProposedRate(room.getRate());
				room.setOccupied(false);
				room.setSelected(false);
				if (!room.isDormitory()) {

					if ((occupiedRoomKeys != null) && occupiedRoomKeys.contains(room.getId())) {
						room.setOccupied(true);
					}
				} else {
					for (Bed bed : room.getBeds()) {

						bed.setSelected(false);
						// Dormitory bed rate is same is Room rate
						bed.setProposedRate(room.getRate());
						bed.setOccupied(false);

						if ((occupiedBedKeys != null) && occupiedBedKeys.contains(bed.getId())) {
							bed.setOccupied(true);
						}
					}
				}
			}
		}
	}

	public List<Building> findBuildingsWithOccupancy() {
		List<Building> buildings = this.buildingService.getBuildings();
		this.populateOccupancy(buildings, this.findOccupiedRoomKeys(), this.findOccupiedBedKeys());
		return buildings;
	}

	@Transactional
	public void allocateRoom(final Date date) {
		this.roomBooking.setCheckInDate(date);
		this.roomBooking.setCheckOutDate(null);
		this.roomBookingDao.persist(this.roomBooking);
		// this.markRoomAllocated(true);
		// this.populateOccupancy(this.getBuildings());
		this.roomBooking = null;
	}

	public void deallocateRoom(Date date) {
		List<RoomBooking> roomBookings = this.roomBookingDao.find("room.id = " + this.getRoomBooking().getRoom().getId()
				+ " and checkinDate is not null and checkOutDate is null");

		if ((roomBookings != null) && (roomBookings.size() > 0)) {

			RoomBooking roomBooking = roomBookings.get(0);
			RoomOccupancyService.LOGGER.debug("Found the RoomBooking " + roomBooking.getId() + " and deallocating");
			roomBooking.setCheckOutDate(date);

			this.roomBookingDao.update(roomBooking);
			// this.markRoomAllocated(false);
			// this.populateOccupancy(this.getBuildings());
			this.roomBooking = null;
		}

	}

	protected void markRoomAllocated(boolean allocate) {
		if (this.occupiedRoomKeys == null) {
			return;
		}
		if (allocate) {
			this.occupiedRoomKeys.add(this.roomBooking.getRoom().getId());
		} else {
			this.occupiedRoomKeys.remove(this.roomBooking.getRoom().getId());
		}

	}

	public void markRoomAllocated(Integer id) {
		if (this.occupiedRoomKeys == null) {
			return;
		}
		this.occupiedRoomKeys.add(id);

	}

	public void markRoomAllocated(Integer id, boolean flag) {
		if (this.occupiedRoomKeys == null) {
			return;
		}
		if (flag) {
			this.occupiedRoomKeys.add(id);
		} else {
			this.occupiedRoomKeys.remove(id);
		}

	}

	@Transactional
	public void allocateBed(final Date date) {
		this.bedBooking.setCheckInDate(date);
		this.bedBooking.setCheckOutDate(null);
		this.bedBooking.setAdmission(this.admissionDao.findById(this.bedBooking.getAdmission().getId()));
		this.bedBookingDao.persist(this.bedBooking);

		// this.markBedAllocated(true);
		// this.populateOccupancy(this.getBuildings());
		this.bedBooking = null;
	}

	@Transactional
	public void deallocateBed(final Date date) {

		List<BedBooking> bedBookings = this.bedBookingDao.find("bed.id = " + this.getBedBooking().getBed().getId()
				+ " and checkinDate is not null and checkOutDate is null");

		if ((bedBookings != null) && (bedBookings.size() > 0)) {

			BedBooking bedBooking = bedBookings.get(0);
			RoomOccupancyService.LOGGER.debug("Found the bedBooking " + bedBooking.getId() + " and deallocating");
			bedBooking.setCheckOutDate(date);

			this.bedBookingDao.update(bedBooking);
			// this.markBedAllocated(false);
			// this.populateOccupancy(this.getBuildings());
			this.bedBooking = null;
		}
	}

	protected void markBedAllocated(boolean allocate) {
		if (this.occupiedBedKeys == null) {
			return;
		}
		if (allocate) {
			this.occupiedBedKeys.add(this.bedBooking.getBed().getId());
		} else {
			this.occupiedBedKeys.remove(this.bedBooking.getBed().getId());
		}
	}

	protected void markBedAllocated(Integer id) {

		if (this.occupiedBedKeys == null) {
			return;
		}
		this.occupiedBedKeys.add(id);
	}

	public void markBedAllocated(Integer id, boolean flag) {

		if (this.occupiedBedKeys == null) {
			return;
		}
		if (flag) {
			this.occupiedBedKeys.add(id);
		} else {
			this.occupiedBedKeys.remove(id);
		}

	}

	@Override
	public void reload() {
		this.buildings = null;
	}

}
